/*
 * Copyright (c) 2009, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tssp_internal.h"
#include "tssp_table.h"
#include "tssp_dvb_sdt.h"


struct tssp_dvb_sdt_monitor {
    struct tssp_table_monitor *tmon;
    tssp_dvb_sdt_monitor_callback callback;
    void *userdata;
};

struct tssp_dvb_sdt *tssp_dvb_sdt_parse(const struct tssp_section *section)
{
    int ret;
    int pos = 0;
    struct tssp_dvb_sdt *sdt;
    struct tssp_dvb_sdt_service *sdt_service;
    struct tssp_table_payload p;
    uint16_t loop_length;
    uint8_t *data;

    TSSP_ASSERT(section);

    if (section->table_id != TSSP_TABLE_ID_DVB_SDT || !section->syntax_indicator)
        return NULL;

    /* prepare data buffer */
    ret = tssp_table_get_payload(&p, section, 0);
    if (ret != TSSP_STATUS_OK)
        return NULL;
    data = p.start;

    /* allocate sdt */
    sdt = tssp_dvb_sdt_create();
    if (sdt == NULL)
        goto cleanup;

    sdt->ts_id = section->extension;
    sdt->version = section->version;
    sdt->current_next = section->current_next;
    sdt->network_id = (data[0] << 8) + data[1];
    pos = 3;

    while (p.length >= (pos + 5)) {
        data = &p.start[pos];
        sdt_service = tssp_dvb_sdt_add_service(sdt,
                                               (data[0] << 8) + data[1],
                                               0x02 & data[2],
                                               0x01 & data[2],
                                               (0xE0 & data[3]) >> 5,
                                               0x10 & data[3]);
        if (sdt_service == NULL)
            goto cleanup;
        loop_length = ((0x0F & data[3]) << 8) + data[4];
        pos += 5;
        /* descriptor loop */
        ret = tssp_descriptor_parse(&sdt_service->descriptors,
                                    &p.start[pos],
                                    p.end,
                                    loop_length);
        if (ret < 0)
            goto cleanup;
        pos += loop_length;
    }
    if (p.length != pos)
        goto cleanup;

    tssp_table_free_payload(&p);
    return sdt;

cleanup:
    tssp_dvb_sdt_destroy(sdt);
    tssp_table_free_payload(&p);
    return NULL;
}

struct tssp_dvb_sdt *tssp_dvb_sdt_create()
{
    struct tssp_dvb_sdt *sdt = malloc(sizeof(*sdt));

    if (sdt)
        STAILQ_INIT(&sdt->services);

    return sdt;
}

void tssp_dvb_sdt_destroy(struct tssp_dvb_sdt *sdt)
{
    struct tssp_dvb_sdt_service *tmp;

    if (sdt) {
        while ((tmp = STAILQ_FIRST(&sdt->services))) {
            STAILQ_REMOVE_HEAD(&sdt->services, entry);
            tssp_dvb_sdt_service_destroy(tmp);
        }
        free(sdt);
    }
}

struct tssp_dvb_sdt_service *
tssp_dvb_sdt_add_service(struct tssp_dvb_sdt *sdt,
                         uint16_t service_id,
                         uint8_t eit_schedule,
                         uint8_t eit_present,
                         uint8_t running_status,
                         uint8_t free_ca_mode)
{
    struct tssp_dvb_sdt_service *sdt_service;

    TSSP_ASSERT(sdt);

    sdt_service = malloc(sizeof(*sdt_service));
    if (sdt_service) {
        sdt_service->service_id = service_id;
        sdt_service->eit_schedule = eit_schedule;
        sdt_service->eit_present = eit_present;
        sdt_service->running_status = running_status;
        sdt_service->free_ca_mode = free_ca_mode;
        STAILQ_INIT(&sdt_service->descriptors);
        STAILQ_INSERT_TAIL(&sdt->services, sdt_service, entry);
    }

    return sdt_service;
}

void tssp_dvb_sdt_service_destroy(struct tssp_dvb_sdt_service *sdt_service)
{
    if (sdt_service) {
        tssp_descriptors_destroy(&sdt_service->descriptors);
        free(sdt_service);
    }
}


static
void tssp_dvb_sdt_monitor_section_callback(struct tssp_table_monitor *tmon,
                                           struct tssp_section *section,
                                           void *userdata)
{
    struct tssp_dvb_sdt_monitor *mon = userdata;
    struct tssp_dvb_sdt *sdt;

    sdt = tssp_dvb_sdt_parse(section);
    if (sdt)
        mon->callback(mon, sdt, section, mon->userdata);
    else
        tssp_section_destroy(section);
}

struct tssp_dvb_sdt_monitor *
tssp_dvb_sdt_monitor_attach(struct tssp *parser,
                            uint16_t ts_id,
                            uint8_t current_next,
                            tssp_dvb_sdt_monitor_callback callback,
                            void *userdata)
{
    struct tssp_dvb_sdt_monitor *monitor;
    struct tssp_section_filter filter;

    if (parser == NULL || callback == NULL)
        return NULL;

    monitor = malloc(sizeof(*monitor));
    if (monitor == NULL)
        return NULL;

    tssp_section_filter_init(&filter, TSSP_PID_DVB_SDT, TSSP_TABLE_ID_DVB_SDT,
                             ts_id, 0xFFFF, 1, 0);
    monitor->tmon = tssp_table_monitor_attach(parser,
                                              &filter,
                                              tssp_dvb_sdt_monitor_section_callback,
                                              monitor);
    if (monitor->tmon == NULL) {
        free(monitor);
        return NULL;
    }

    monitor->callback = callback;
    monitor->userdata = userdata;

    return monitor;
}

void tssp_dvb_sdt_monitor_detach(struct tssp_dvb_sdt_monitor *monitor)
{
    if (monitor == NULL)
        return;

    TSSP_ASSERT(monitor->tmon);

    tssp_dvb_sdt_monitor_reset(monitor);
    tssp_table_monitor_detach(monitor->tmon);
    free(monitor);
}

void tssp_dvb_sdt_monitor_reset(struct tssp_dvb_sdt_monitor *mon)
{
    TSSP_ASSERT(mon);
    tssp_table_monitor_reset(mon->tmon);
}

